package com.example.forum.service.impl;

import com.example.forum.common.AppResult;
import com.example.forum.common.ResultCode;
import com.example.forum.dao.ArticleMapper;
import com.example.forum.exception.ApplicationException;
import com.example.forum.model.Article;
import com.example.forum.model.Board;
import com.example.forum.model.User;
import com.example.forum.service.IArticleService;
import com.example.forum.service.IBoardService;
import com.example.forum.service.IUserService;
import com.example.forum.utils.IdNotNullUtil;
import com.example.forum.utils.RedisUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.stream.Collectors;

import com.example.forum.dto.HotArticleDTO;
import com.example.forum.common.PageResult;

/**
 * 功能描述:
 *
 * @author Lenovo
 * @date 2024/12/13
 */
@Slf4j
@Service
public class ArticleServiceImpl implements IArticleService {
    @Resource
    private ArticleMapper articleMapper;
    @Resource
    private IUserService userService;
    @Resource
    private IBoardService boardService;
    @Resource
    private RedisUtil redisUtil;

    private static final String HOT_ARTICLES_KEY = "hot_articles";           // ZSet存储文章分数
    private static final String ARTICLE_INFO_KEY = "article_info";           // Hash存储文章信息
    private static final String LAST_UPDATE_TIME_KEY = "hot_articles_last_update";
    private static final String ARTICLE_VISIT_KEY = "article_visit:";        // 访问量 KEY
    private static final String ARTICLE_LIKE_KEY = "article_like:";        // 访问量 KEY
    private static final int UPDATE_INTERVAL = 8 * 3600;                     // 更新间隔，8小时
    private static final int MAX_ARTICLES_IN_REDIS = 100;                    // Redis中最多保存100篇文章
    private static final int FEATURED_ARTICLES_COUNT = 10;                   // 精华文章数量

    // 与数据库查询保持一致的权重设置
    private static final double VISIT_WEIGHT = 0.4;  // 访问量权重 40%
    private static final double LIKE_WEIGHT = 0.4;   // 点赞数权重 40%
    private static final double REPLY_WEIGHT = 0.2;  // 回复数权重 20%

    @Override
    public void create(Article article) {
        if (article == null || article.getUserId() == null
                || article.getBoardId() == null
                || !StringUtils.hasLength(article.getTitle())
                || !StringUtils.hasLength(article.getContent())) {
            log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
        }
        // 设置默认值
        article.setVisitCount(0); // 访问数
        article.setReplyCount(0); // 回复数
        article.setLikeCount(0);  // 点赞数
        article.setDeleteState((byte) 0);
        article.setState((byte) 0);
        Date date = new Date();
        article.setCreateTime(date);
        article.setUpdateTime(date);

        int articleRow = articleMapper.insertSelective(article);
        if (articleRow <= 0) {
            log.warn(ResultCode.FAILED_CREATE.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));
        }
        //获取用户信息
        User user = userService.selectById(article.getUserId());
        if (user == null) {
            log.warn(ResultCode.FAILED_CREATE.toString() + ",发帖失败，userId：" + article.getUserId());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));
        }
        //更新用户发帖数
        userService.addOneArticleCountById(user.getId());

        //获取板块信息
        Board board = boardService.selectById(article.getBoardId());
        if (board == null) {
            log.warn(ResultCode.FAILED_CREATE.toString() + ",发帖失败，boardId：" + article.getBoardId());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));
        }
        //更新板块帖子数
        boardService.addOneArticleCountId(board.getId());
        log.info(ResultCode.SUCCESS.toString() + ",userId：" + article.getUserId()
                + ",boardId: " + article.getBoardId()
                + ",articleId: " + article.getId() + ",发帖成功");

        // 更新Redis中的文章分数
        double score = calculateArticleScore(article);
        redisUtil.zAdd(HOT_ARTICLES_KEY, String.valueOf(article.getId()), score);
        saveArticleToRedis(article);
    }

    @Override
    public List<Article> selectAll() {
        return articleMapper.selectAll();
    }

    @Override
    public List<Article> selectAllByBoardId(Long boardId) {
        if (boardId == null || boardId <= 0) {
            log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
        }

        //先查看板块是否存在，如果不存在就不用进行联表查询
        Board board = boardService.selectById(boardId);
        if (board == null) {
            log.warn(ResultCode.FAILED_BOARD_NOT_EXISTS.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_BOARD_NOT_EXISTS));
        }

        //板块存在，直接返回查询到的结果
        return articleMapper.selectAllByBoardId(boardId);
    }

    @Override
    public Article selectDetailById(Long id, Long userId) {
        IdNotNullUtil.idNotNullUtil(id);

        // 构造Redis key，只使用用户ID作为标识
        String visitorKey = ARTICLE_VISIT_KEY + id + ":user:" + userId;

        String visitResult = redisUtil.get(visitorKey);
        Article article = articleMapper.selectDetailById(id);

        if (article == null) {
            log.warn(ResultCode.FAILED_ARTICLE_NOT_EXISTS.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS));
        }

        // 如果没有访问记录，则更新访问量
        if (visitResult == null) {
            // 更新数据库中帖子访问次数
            Article updateArticle = new Article();
            updateArticle.setId(article.getId());
            updateArticle.setVisitCount(article.getVisitCount() + 1);

            int row = articleMapper.updateByPrimaryKeySelective(updateArticle);
            log.info("影响行数：" + row);
            if (row != 1) {
                log.warn(ResultCode.ERROR_SERVICES.toString());
                throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
            }

            // 更新返回对象的访问次数
            article.setVisitCount(article.getVisitCount() + 1);
            // 设置访问记录，60秒内不重复计数
            redisUtil.set(visitorKey, "1", 60L);
        }

        return article;
    }

    @Override
    public void modify(Long id, String title, String content) {
        if (id == null || id <= 0 ||
                !StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
            log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
        }
        Article updateArticle = articleMapper.selectByPrimaryKey(id);
        updateArticle.setId(id);
        updateArticle.setTitle(title);
        updateArticle.setContent(content);
        updateArticle.setUpdateTime(new Date());

        int row = articleMapper.updateByPrimaryKeySelective(updateArticle);

        if (row != 1) {
            log.warn(ResultCode.ERROR_SERVICES.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
        }

        // 更新Redis中的分数
        updateArticleHotScore(updateArticle);
    }

    @Override
    public Article selectById(Long id) {
        IdNotNullUtil.idNotNullUtil(id);
        return articleMapper.selectByPrimaryKey(id);
    }

    @Override
    public List<Article> selectByUserId(Long userId) {
        IdNotNullUtil.idNotNullUtil(userId);
        //判断user是否存在
        User user = userService.selectById(userId);
        if (user == null) {
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS));
        }
        return articleMapper.selectByUserId(userId);
    }

    @Override
    public boolean thumbsUpById(Long id, Long userId) {
        IdNotNullUtil.idNotNullUtil(id);
        IdNotNullUtil.idNotNullUtil(userId);

        // 构造Redis key，使用用户ID和文章ID作为唯一标识
        String likeKey = ARTICLE_LIKE_KEY + id + ":user:" + userId;

        Article article = articleMapper.selectByPrimaryKey(id);
        if (article == null || article.getDeleteState() == 1) {
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS));
        }

        if (article.getState() == 1) {
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_BANNED));
        }
        
        Article updateArticle = new Article();
        updateArticle.setId(id);
        boolean isLiked;
        
        //取消点赞
        if (redisUtil.hasKey(likeKey)) {
            updateArticle.setLikeCount(article.getLikeCount() - 1);
            redisUtil.del(likeKey);
            isLiked = false;
        } else {
            updateArticle.setLikeCount(article.getLikeCount() + 1);
            redisUtil.set(likeKey, "1");
            isLiked = true;
        }
        
        int row = articleMapper.updateByPrimaryKeySelective(updateArticle);
        if (row != 1) {
            log.warn(ResultCode.ERROR_SERVICES.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
        }
        
        // 更新Redis中的分数
        updateArticleHotScore(updateArticle);
        // 更新Redis中的文章信息
        Article fullArticle = articleMapper.selectDetailById(id);
        updateArticleInRedis(fullArticle);
        
        return isLiked;
    }

    @Override
    public void deleteById(Long id) {
        IdNotNullUtil.idNotNullUtil(id);

        Article article = articleMapper.selectByPrimaryKey(id);
        if (article == null || article.getId() == null || article.getDeleteState() == 1) {
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_BANNED.toString()));
        }

        //删除帖子
        Article updateArticle = new Article();
        updateArticle.setId(id);
        updateArticle.setDeleteState((byte) 1);
        int row = articleMapper.updateByPrimaryKeySelective(updateArticle);
        if (row != 1) {
            throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES.toString()));
        }

        userService.subOneArticleCountById(article.getUserId());
        boardService.subOneArticleCountById(article.getBoardId());

        // 从Redis中删除文章
        redisUtil.zRemove(HOT_ARTICLES_KEY, String.valueOf(id));
    }

    @Override
    public void addOneReplyCountById(Long id) {
        IdNotNullUtil.idNotNullUtil(id);

        Article article = articleMapper.selectByPrimaryKey(id);
        //帖子不存在
        if (article == null || article.getDeleteState() == 1) {
            log.warn(ResultCode.FAILED_ARTICLE_NOT_EXISTS.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS));
        }
        //帖子被封
        if (article.getState() == 1) {
            log.warn(ResultCode.FAILED_ARTICLE_BANNED.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_BANNED));
        }

        //构造更新对象
        Article updateArticle = new Article();
        updateArticle.setId(article.getId());
        updateArticle.setReplyCount(article.getReplyCount() + 1);
        updateArticle.setUpdateTime(new Date());

        //更新帖子
        int row = articleMapper.updateByPrimaryKeySelective(updateArticle);
        if (row != 1) {
            throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
        }

        // 更新Redis中的分数
        updateArticleHotScore(updateArticle);
    }

    @Override
    public List<Article> selectArticlesByTitleLike(String title) {
        if (!StringUtils.hasLength(title)) {
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
        }
        String param = "%" + title + "%";
        return articleMapper.selectArticlesByTitleLike(param);
    }

    @Override
    public List<HotArticleDTO> getFeaturedArticles() {
        // 检查并清理旧文章
        cleanupOldArticles();

        // 从ZSet获取前10篇热门文章ID
        Set<String> articleIds = redisUtil.zReverseRange(HOT_ARTICLES_KEY, 0, FEATURED_ARTICLES_COUNT - 1);
        List<HotArticleDTO> featuredArticles = new ArrayList<>();

        if (articleIds != null && !articleIds.isEmpty()) {
            // 从Redis获取文章详细信息
            log.info("getFeaturedArticles（）从 redis 中获取到数据...");
            for (String articleId : articleIds) {
                String hashKey = ARTICLE_INFO_KEY + ":" + articleId;
                Map<Object, Object> articleInfo = redisUtil.hmget(hashKey);

                if (articleInfo != null && !articleInfo.isEmpty()) {
                    // 从Redis构建DTO
                    HotArticleDTO dto = HotArticleDTO.builder()
                            .id(Long.parseLong(articleId))
                            .title((String) articleInfo.get("title"))
                            .authorName((String) articleInfo.get("authorName"))
                            .authorAvatar((String) articleInfo.get("authorAvatar"))
                            .hotScore((String) articleInfo.get("score"))
                            .visitCount(Integer.parseInt((String) articleInfo.get("visitCount")))
                            .likeCount(Integer.parseInt((String) articleInfo.get("likeCount")))
                            .replyCount(Integer.parseInt((String) articleInfo.get("replyCount")))
                            .build();
                    featuredArticles.add(dto);
                } else {
                    // Redis中没有详细信息，从数据库获取
                    Article article = articleMapper.selectDetailById(Long.parseLong(articleId));
                    if (article != null) {
                        // 更新Redis中的文章信息
                        saveArticleToRedis(article);
                        // 构建DTO
                        HotArticleDTO dto = HotArticleDTO.builder()
                                .id(article.getId())
                                .title(article.getTitle())
                                .authorName(article.getUser() != null ? article.getUser().getNickname() : "未知用户")
                                .authorAvatar(article.getUser() != null ? article.getUser().getAvatarUrl() : null)
                                .hotScore(String.format("%.1f", calculateArticleScore(article)))
                                .visitCount(article.getVisitCount())
                                .likeCount(article.getLikeCount())
                                .replyCount(article.getReplyCount())
                                .build();
                        featuredArticles.add(dto);
                    }
                }
            }

            if (featuredArticles.size() == FEATURED_ARTICLES_COUNT) {
                return featuredArticles;
            }
        }

        // Redis中的文章不够，从数据库获取
        List<Article> dbArticles = articleMapper.selectFeaturedArticles();
        if (dbArticles != null && !dbArticles.isEmpty()) {
            // 更新Redis缓存并构建DTO列表
            featuredArticles = dbArticles.stream()
                    .map(article -> {
                        saveArticleToRedis(article);
                        return HotArticleDTO.builder()
                                .id(article.getId())
                                .title(article.getTitle())
                                .authorName(article.getUser() != null ? article.getUser().getNickname() : "未知用户")
                                .authorAvatar(article.getUser() != null ? article.getUser().getAvatarUrl() : null)
                                .hotScore(String.format("%.1f", calculateArticleScore(article)))
                                .visitCount(article.getVisitCount())
                                .likeCount(article.getLikeCount())
                                .replyCount(article.getReplyCount())
                                .build();
                    })
                    .collect(Collectors.toList());

            log.info("从数据库中获取热门文章并更新到Redis，共 {} 篇", dbArticles.size());
        }

        return featuredArticles;
    }

    private void cleanupOldArticles() {
        String lastUpdateTimeStr = redisUtil.get(LAST_UPDATE_TIME_KEY);
        long currentTime = System.currentTimeMillis() / 1000;

        if (lastUpdateTimeStr != null) {
            long lastUpdateTime = Long.parseLong(lastUpdateTimeStr);
            if (currentTime - lastUpdateTime < UPDATE_INTERVAL) {
                return; // 未到更新时间
            }
        }

        try {
            // 获取所有文章的数量
            Long totalSize = redisUtil.zSize(HOT_ARTICLES_KEY);
            if (totalSize == null || totalSize == 0) {
                return;
            }

            // 如果文章数量超过限制，只保留分数最高的100篇
            if (totalSize > MAX_ARTICLES_IN_REDIS) {
                // 获取需要删除的低分文章（保留分数最高的100篇）
                Set<String> lowScoreArticles = redisUtil.zRange(HOT_ARTICLES_KEY, 0, totalSize - MAX_ARTICLES_IN_REDIS - 1);
                if (lowScoreArticles != null && !lowScoreArticles.isEmpty()) {
                    redisUtil.zRemove(HOT_ARTICLES_KEY, lowScoreArticles.toArray());
                    log.info("从Redis中清理了 {} 篇低分文章，当前保留 {} 篇文章", lowScoreArticles.size(), MAX_ARTICLES_IN_REDIS);
                }
            }

            // 更新最后清理时间
            redisUtil.set(LAST_UPDATE_TIME_KEY, String.valueOf(currentTime));
            log.info("文章清理完成，清理时间：{}", currentTime);
        } catch (Exception e) {
            log.error("文章清理过程中发生错误: ", e);
        }
    }

    private void initializeRedisArticles() {
        // 如果Redis为空，初始化所有文章
        if (redisUtil.zSize(HOT_ARTICLES_KEY) == 0) {
            List<Article> allArticles = articleMapper.selectAll();
            for (Article article : allArticles) {
                double score = calculateArticleScore(article);
                redisUtil.zAdd(HOT_ARTICLES_KEY, String.valueOf(article.getId()), score);
            }
            log.info("Redis初始化完成，共初始化 {} 篇文章", allArticles.size());
        }
    }

    // 更新文章在Redis中的分数
    private void updateArticleHotScore(Article article) {
        if (article != null && article.getId() != null) {
            double score = calculateArticleScore(article);
            redisUtil.zAdd(HOT_ARTICLES_KEY, String.valueOf(article.getId()), score);

            // 每次更新分数时检查是否需要清理
            cleanupOldArticles();
        }
    }

    /**
     * 计算文章热度分数
     * 计算公式：访问量 * 0.4 + 点赞数 * 0.4 + 回复数 * 0.2
     * 与数据库查询保持一致的权重计算方式
     */
    private double calculateArticleScore(Article article) {
        if (article == null) {
            return 0.0;
        }

        // 防止空指针，设置默认值为0
        int visitCount = article.getVisitCount() != null ? article.getVisitCount() : 0;
        int likeCount = article.getLikeCount() != null ? article.getLikeCount() : 0;
        int replyCount = article.getReplyCount() != null ? article.getReplyCount() : 0;

        return visitCount * VISIT_WEIGHT
                + likeCount * LIKE_WEIGHT
                + replyCount * REPLY_WEIGHT;
    }

    /**
     * 文章信息存储到Redis
     */
    private void saveArticleToRedis(Article article) {
        if (article == null || article.getId() == null) {
            return;
        }

        String articleId = String.valueOf(article.getId());
        double score = calculateArticleScore(article);

        // 存储文章分数到ZSet
        redisUtil.zAdd(HOT_ARTICLES_KEY, articleId, score);

        // 构建文章基本信息
        Map<String, String> articleInfo = new HashMap<>();
        articleInfo.put("id", articleId);
        articleInfo.put("title", article.getTitle());
        articleInfo.put("authorName", article.getUser() != null ? article.getUser().getNickname() : "未知用户");
        articleInfo.put("authorAvatar", article.getUser() != null && StringUtils.hasLength(article.getUser().getAvatarUrl())
                ? article.getUser().getAvatarUrl()
                : "image/avatar01.jpeg");
        articleInfo.put("visitCount", String.valueOf(article.getVisitCount()));
        articleInfo.put("likeCount", String.valueOf(article.getLikeCount()));
        articleInfo.put("replyCount", String.valueOf(article.getReplyCount()));
        articleInfo.put("score", String.format("%.1f", score));

        // 存储文章信息到Hash
        String hashKey = ARTICLE_INFO_KEY + ":" + articleId;
        redisUtil.hmset(hashKey, articleInfo);
        redisUtil.expire(hashKey, UPDATE_INTERVAL + 3600);
    }

    // 在其他更新文章的方法中也需要更新Redis信息
    private void updateArticleInRedis(Article article) {
        if (article != null && article.getId() != null) {
            // 先删除旧的Hash信息
            String hashKey = ARTICLE_INFO_KEY + ":" + article.getId();
            redisUtil.del(hashKey);
            // 保存新的文章信息
            saveArticleToRedis(article);
        }
    }

    @Override
    public PageResult<Article> selectPage(long current, long size) {
        // 计算偏移量
        long offset = (current - 1) * size;
        // 查询当前页数据
        List<Article> records = articleMapper.selectPage(offset, size);
        // 查询总记录数
        long total = articleMapper.selectCount();
        // 返回分页结果
        return new PageResult<>(records, total, size, current);
    }

    @Override
    public PageResult<Article> selectPageByBoardId(Long boardId, long current, long size) {
        if (boardId == null || boardId <= 0) {
            log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
        }

        //先查看板块是否存在
        Board board = boardService.selectById(boardId);
        if (board == null) {
            log.warn(ResultCode.FAILED_BOARD_NOT_EXISTS.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_BOARD_NOT_EXISTS));
        }

        // 计算偏移量
        long offset = (current - 1) * size;
        // 查询当前页数据
        List<Article> records = articleMapper.selectPageByBoardId(boardId, offset, size);
        // 查询总记录数
        long total = articleMapper.selectCountByBoardId(boardId);
        // 返回分页结果
        return new PageResult<>(records, total, size, current);
    }

    @Override
    public PageResult<Article> selectPageByUserId(Long userId, long current, long size) {
        if (userId == null || userId <= 0) {
            log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
        }

        //判断user是否存在
        User user = userService.selectById(userId);
        if (user == null) {
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS));
        }

        // 计算偏移量
        long offset = (current - 1) * size;
        // 查询当前页数据
        List<Article> records = articleMapper.selectPageByUserId(userId, offset, size);
        // 查询总记录数
        long total = articleMapper.selectCountByUserId(userId);
        // 返回分页结果
        return new PageResult<>(records, total, size, current);
    }
}
